/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
/*
 * Allwinner SoCs display driver.
 *
 * Copyright (C) 2016 Allwinner.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include "disp_smart_backlight.h"

struct disp_smbl_private_data {
	struct disp_smbl_info info;
	bool applied;

	 s32 (*shadow_protect)(u32 sel, bool protect);

	u32 enabled;
	struct mutex mlock;
};
static spinlock_t smbl_data_lock;

/* #define SMBL_NO_AL */
static struct disp_smbl *smbls;
static struct disp_smbl_private_data *smbl_private;

struct disp_smbl *disp_get_smbl(u32 disp)
{
	u32 num_screens;

	num_screens = bsp_disp_feat_get_num_screens();
	if (disp >= num_screens) {
		DE_WARN("disp %d out of range\n", disp);
		return NULL;
	}
	DE_INFO("get smbl%d ok\n", disp);

	if (bsp_disp_feat_is_support_smbl(disp))
		return &smbls[disp];
	else
		return NULL;
}
static struct disp_smbl_private_data *disp_smbl_get_priv(struct disp_smbl *smbl)
{
	if (smbl == NULL) {
		DE_INFO("NULL hdl\n");
		return NULL;
	}

	return &smbl_private[smbl->disp];
}

static s32 disp_smbl_update_regs(struct disp_smbl *smbl)
{
	unsigned long flags;
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	bool applied = false;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	spin_lock_irqsave(&smbl_data_lock, flags);
	if (true == smblp->applied) {
		applied = true;
		smblp->applied = false;
	}
	spin_unlock_irqrestore(&smbl_data_lock, flags);

	disp_al_smbl_update_regs(smbl->disp);

	return 0;
}

/* should protect width @mlock */
static s32 disp_smbl_update_backlight(struct disp_smbl *smbl, unsigned int bl)
{
	if (smbl == NULL) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	smbl->backlight = bl;
	smbl->apply(smbl);

	return 0;
}

/* should protect width @mlock */
static s32 disp_smbl_apply(struct disp_smbl *smbl)
{
	unsigned long flags;
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	struct disp_smbl_info info;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	memset(&info, 0, sizeof(struct disp_smbl_info));

	/* mutex_lock(&smblp->mlock); */
	if (smbl->backlight != smblp->info.backlight) {
		smblp->info.backlight = smbl->backlight;
		smblp->info.flags |= SMBL_DIRTY_BL;
	}
	if (smblp->info.flags != SMBL_DIRTY_NONE) {
		memcpy(&info, &smblp->info, sizeof(struct disp_smbl_info));
		smblp->info.flags = SMBL_DIRTY_NONE;
		disp_smbl_shadow_protect(smbl, true);
		disp_al_smbl_apply(smbl->disp, &info);
		disp_smbl_shadow_protect(smbl, false);
		spin_lock_irqsave(&smbl_data_lock, flags);
		smblp->applied = true;
		spin_unlock_irqrestore(&smbl_data_lock, flags);
	}
	/* mutex_unlock(&smblp->mlock); */

	return 0;
}

static s32 disp_smbl_force_apply(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	mutex_lock(&smblp->mlock);
	smblp->info.flags = SMBL_DIRTY_ALL;
	disp_smbl_apply(smbl);
	disp_smbl_update_regs(smbl);
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_sync(struct disp_smbl *smbl)
{
	if (smbl == NULL) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	if (disp_feat_is_using_rcq(smbl->disp))
		return 0;

	disp_smbl_update_regs(smbl);

	return 0;
}

static s32 disp_smbl_tasklet(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	struct disp_manager *mgr;
	unsigned int dimming;
	bool dimming_update = false;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	disp_al_smbl_tasklet(smbl->disp);
	dimming = disp_al_smbl_get_status(smbl->disp);
	if (smblp->info.backlight_dimming != dimming) {
		smblp->info.backlight_dimming = dimming;
		dimming_update = true;
	}

	mgr = smbl->manager;
	if (mgr && mgr->device) {
		struct disp_device *dispdev = mgr->device;

		if (dispdev->set_bright_dimming && dimming_update) {
			if (dispdev->set_bright_dimming)
				dispdev->set_bright_dimming(dispdev,
						smblp->info.backlight_dimming);
		}
	}

	return 0;
}

static bool disp_smbl_is_enabled(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return false;
	}

	return (smblp->info.enable == 1);
}

static s32 disp_smbl_enable(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	struct disp_device *dispdev = NULL;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	if (smbl->manager)
		dispdev = smbl->manager->device;
	if (dispdev)
		dispdev->get_resolution(dispdev, &smblp->info.size.width,
					&smblp->info.size.height);

	if ((smblp->info.window.width == 0)
	    || (smblp->info.window.height == 0)) {
		smblp->info.window.width = smblp->info.size.width;
		smblp->info.window.height = smblp->info.size.height;
	}

	DE_INFO("smbl %d enable\n", smbl->disp);
	mutex_lock(&smblp->mlock);
	smblp->info.enable = 1;
	smblp->info.flags |= SMBL_DIRTY_ENABLE;
	disp_smbl_apply(smbl);
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_disable(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	DE_INFO("smbl %d disable\n", smbl->disp);

	mutex_lock(&smblp->mlock);
	smblp->info.enable = 0;
	smblp->info.flags |= SMBL_DIRTY_ENABLE;
	disp_smbl_apply(smbl);
	mutex_unlock(&smblp->mlock);

	return 0;
}

s32 disp_smbl_shadow_protect(struct disp_smbl *smbl, bool protect)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	struct disp_manager *mgr;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	mgr = smbl->manager;
	if (mgr && mgr->reg_protect)
		return mgr->reg_protect(mgr, protect);
	if (smblp->shadow_protect)
		return smblp->shadow_protect(smbl->disp, protect);

	return -1;
}

static s32 disp_smbl_set_window(struct disp_smbl *smbl,
				struct disp_rect *window)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	mutex_lock(&smblp->mlock);
	memcpy(&smblp->info.window, window, sizeof(struct disp_rect));
	smblp->info.flags |= SMBL_DIRTY_WINDOW;
	disp_smbl_apply(smbl);
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_get_window(struct disp_smbl *smbl,
				struct disp_rect *window)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	mutex_lock(&smblp->mlock);
	memcpy(window, &smblp->info.window, sizeof(struct disp_rect));
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_set_manager(struct disp_smbl *smbl,
				 struct disp_manager *mgr)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (mgr == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	DE_INFO("smbl %d -> mgr %d\n", smbl->disp, mgr->disp);
	mutex_lock(&smblp->mlock);
	smbl->manager = mgr;
	mgr->smbl = smbl;
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_unset_manager(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}
	mutex_lock(&smblp->mlock);
	if (smbl->manager)
		smbl->manager->smbl = NULL;
	smbl->manager = NULL;
	mutex_unlock(&smblp->mlock);

	return 0;
}

static s32 disp_smbl_dump(struct disp_smbl *smbl, char *buf)
{
	struct disp_smbl_info info;
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);
	u32 count = 0;

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	memcpy(&info, &smblp->info, sizeof(struct disp_smbl_info));

	count +=
	    sprintf(buf + count,
		    "smart_backlight %d: %s, window<%d,%d,%d,%d>, backlight=%d, save_power=%d percent\n",
		    smbl->disp, (info.enable == 1) ? "enable" : "disable",
		    info.window.x, info.window.y, info.window.width,
		    info.window.height, smbl->backlight,
		    100 - info.backlight_dimming * 100 / 256);

	return count;
}

static s32 disp_smbl_init(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	return 0;
}

static s32 disp_smbl_exit(struct disp_smbl *smbl)
{
	struct disp_smbl_private_data *smblp = disp_smbl_get_priv(smbl);

	if ((smbl == NULL) || (smblp == NULL)) {
		DE_INFO("NULL hdl\n");
		return -1;
	}

	return 0;
}

s32 disp_init_smbl(struct disp_bsp_init_para *para)
{
	u32 num_smbls;
	u32 disp;
	struct disp_smbl *smbl;
	struct disp_smbl_private_data *smblp;

	DE_INFO("disp_init_smbl\n");

	spin_lock_init(&smbl_data_lock);
	num_smbls = bsp_disp_feat_get_num_screens();
	smbls =
	    kmalloc_array(num_smbls, sizeof(struct disp_smbl),
			  GFP_KERNEL | __GFP_ZERO);
	if (smbls == NULL) {
		DE_WARN("malloc memory fail\n");
		goto malloc_err;
	}
	smbl_private =
	    (struct disp_smbl_private_data *)
	    kmalloc(sizeof(struct disp_smbl_private_data)
		    * num_smbls, GFP_KERNEL | __GFP_ZERO);
	if (smbl_private == NULL) {
		DE_WARN("malloc memory fail\n");
		goto malloc_err;
	}

	for (disp = 0; disp < num_smbls; disp++) {
		if (!bsp_disp_feat_is_support_smbl(disp))
			continue;

		smbl = &smbls[disp];
		smblp = &smbl_private[disp];
		mutex_init(&smblp->mlock);

		switch (disp) {
		case 0:
			smbl->name = "smbl0";
			smbl->disp = 0;

			break;
		case 1:
			smbl->name = "smbl1";
			smbl->disp = 1;

			break;
		case 2:
			smbl->name = "smbl2";
			smbl->disp = 2;

			break;
		}
		smblp->shadow_protect = para->shadow_protect;

		smbl->enable = disp_smbl_enable;
		smbl->disable = disp_smbl_disable;
		smbl->is_enabled = disp_smbl_is_enabled;
		smbl->init = disp_smbl_init;
		smbl->exit = disp_smbl_exit;
		smbl->apply = disp_smbl_apply;
		smbl->force_apply = disp_smbl_force_apply;
		smbl->update_regs = disp_smbl_update_regs;
		smbl->sync = disp_smbl_sync;
		smbl->tasklet = disp_smbl_tasklet;
		smbl->set_manager = disp_smbl_set_manager;
		smbl->unset_manager = disp_smbl_unset_manager;
		smbl->set_window = disp_smbl_set_window;
		smbl->get_window = disp_smbl_get_window;
		smbl->update_backlight = disp_smbl_update_backlight;
		smbl->dump = disp_smbl_dump;

		smbl->init(smbl);
	}

	return 0;

malloc_err:
	kfree(smbl_private);
	kfree(smbls);
	smbl_private = NULL;
	smbls = NULL;

	return -1;
}

s32 disp_exit_smbl(void)
{
	u32 num_smbls;
	u32 disp;
	struct disp_smbl *smbl;

	if (!smbls)
		return 0;

	num_smbls = bsp_disp_feat_get_num_screens();
	for (disp = 0; disp < num_smbls; disp++) {
		if (!bsp_disp_feat_is_support_smbl(disp))
			continue;

		smbl = &smbls[disp];
		smbl->exit(smbl);
	}

	kfree(smbl_private);
	kfree(smbls);
	smbl_private = NULL;
	smbls = NULL;

	return 0;
}
